-----------Leisure Suit Larry----------
---in the Land of the Lounge Lizards---
A 4am and san inc crack      2018-01-15
---------------------------------------

Name: Leisure Suit Larry in the Land of
  the Lound Lizards
Genre: adventure
Year: 1987
Credits: written by Al Lowe; designed
  by Chuck Benton, Mark Crowe, and Al
  Lowe; graphics by Mark Crowe; music
  by Al Lowe; game development system
  by Jeff Stephenson, Chris Iden, and
  Bob Heitman; cameo coding by Ken
  Williams; Apple II version by Carlos
  Escobar
Publisher: Sierra On-Line
Platform: Apple //e or later (128K)
Media: 3 double-sided 5.25-inch disks
  (5 sides total)
OS: DOS 3.3

This disk was automatically cracked by
Passport, using qkumba's universal
Sierra patcher. Here is the transcript
for disk 1A:

                 --v--

READING FROM S6,D1
T00,S00 FOUND DOS 3.3 BOOTLOADER
USING DISK'S OWN RWTS
WRITING TO S5,D2
T17,S00 FOUND SIERRA PROTECTION CHECK
T17,S00,$09: 20 -> 60
CRACK COMPLETE.

                 --^--

More information and source code is
available at
https://archive.org/details/Passport4am

Other sides are unprotected.

Quod erat liberand one more thing...

                   ~

Every DOS 3.3-formatted Apple II floppy
has a "disk volume number." It is set
when you format the disk, is stored in
the address field of every sector, and
is displayed when you issue the CATALOG
command. Passport now finds and logs
the disk volume number on DOS 3.3 disks
(as seen above). The disk volume number
is almost always ignoreable, except
when it isn't.

In this game, it isn't.

All 5 disks, including the boot disk,
and even the data disks you create to
save and restore games, have non-
standard disk volume numbers.

  - disk 1A: 001
  - disk 1B: 002
  - disk 2A: 003
  - disk 2B: 004
  - disk 3A: 005
  - data disks: 015

And they matter. The game uses the disk
volume number to know whether the right
disk is in the drive at any particular
time.

Why is this a problem? In the physical
world, it isn't. Unprotected disks can
have any disk volume number and still
be copyable with third-party tools. But
it's 2018, and we don't traffic in
physical objects anymore; we traffic in
disk images.

Due to poor historical choices, the
dominant format for disk images, .DSK,
does not store the disk volume number.
This presents us with a modern dilemma
of our own design: either release disk
images in some other format (easy, but
could reduce compatibility or require
more disk space or both), or change the
game code to distinguish disks in some
other way (hard, game-specific, insane,
why would you do that).

Obviously, we chose the hard way.

To distinguish disks that are really
.DSK files, we need to store a unique
marker somewhere on the disk that will
actually be stored in the .DSK file.
Since .DSK files only contain sector
data from track $00 to track $22, that
means we need to store a byte in a
sector. That part is non-negotiable.
Data must be stored somewhere, and if
.DSK files only store sector data, we
need to store the fake volume number as
sector data.

Of course, the game doesn't make this
easy for us. There isn't actually one
sector that is unused across all disks.
(Trust me, we checked.) But that's not
even the worst part.

This game has an in-game command to
format a data disk. The data disk is
formatted with -- you guessed it -- a
nonstandard disk volume number, which
is later checked to ensure the data
disk is in the drive during the "SAVE
GAME" or "RESTORE GAME" command.

Here's what we're going to do. We're
going to store the disk volume number
in a sector that has real data,

AND intercept the game's low-level disk
reading routine so that instead of
getting the disk volume from the
address field, it gets it from that
magic byte in that magic sector,

AND intercept low-level format commands
to write the disk volume number to that
magic byte of that magic sector,

AND intercept low-level read commands
of that magic sector to return the real
sector data that belongs there instead
of the magic byte,

AND cache the magic byte from the magic
sector so we're not actually reading
the magic sector for every single disk
access (which would totally destroy the
performance of an already slow medium).

I said it was insane. You were warned.

                   ~

Our code begins at $BEB0. This region
is supposed to be part of the format
disk routine, but it is now unused for
reasons that will be explained shortly.

[Note: source code for this routine was
 provided along with this write-up. If
 you didn't get it, well, I don't know
 what to tell you. Caveat emptor.]

[Note the second: qkumba wrote this
 code. I mostly just cheered.]

*BEB0L

; Save address of RWTS parameter table
BEB0-   84 48       STY   $48
BEB2-   85 49       STA   $49

; Check the requested disk volume
BEB4-   A0 03       LDY   #$03
BEB6-   B1 48       LDA   ($48),Y

; 0 = wildcard = always continue
BEB8-   F0 57       BEQ   $BF11

; Otherwise check if it's $FE
BEBA-   AA          TAX
BEBB-   E8          INX
BEBC-   E0 FF       CPX   #$FF

; No, it's a non-standard disk volume
BEBE-   90 02       BCC   $BEC2

; Yes, it was $FE -- change it to $01
; to fake out the caller (the game will
; refuse to boot on a disk with the
; wrong disk volume)
BEC0-   A9 01       LDA   #$01

; Either way, store the requested disk
; volume because we'll need it later
BEC2-   8D 0B BF    STA   $BF0B

; If we're trying to read the boot disk,
; skip ahead (the carry is still set
; from the CMP at $BEBC)
BEC5-   B0 4A       BCS   $BF11

; The other disks need special handling
; because they can be requested at any
; time. First off, fake out the real
; RWTS by putting a 0 in the requested
; disk volume
BEC7-   A9 00       LDA   #$00
BEC9-   91 48       STA   ($48),Y

BECB-   CA          DEX

; X holds the requested disk volume --
; check if it's the same as the last
; RWTS command
BECC-   EC 0D BF    CPX   $BF0D

; Yes, they match, which means we can
; go ahead and execute the RWTS command
BECF-   F0 40       BEQ   $BF11

; No, this RWTS command is requesting a
; different disk volume than the last
; RWTS command. It's time for our magic
; act!
BED1-   20 3D BF    JSR   $BF3D

Track $22, sector $0F is the best
choice to store our fake disk volume
byte, for one reason: it is unused on
the save game disk. None of the other
disks are writeable, so we won't have
to intercept writes to the sector,
which simplifies the code enormously.

*BF3DL

; swap out the requested track/sector/
; command with
;   track = $22
;   sector = $0F
;   command = $01 (read)
; and save the original values so we
; can restore them later
BF3D-   A2 04       LDX   #$04
BF3F-   BC 54 BF    LDY   $BF54,X
BF42-   B1 48       LDA   ($48),Y
BF44-   48          PHA
BF45-   BD 53 BF    LDA   $BF53,X
BF48-   91 48       STA   ($48),Y
BF4A-   68          PLA
BF4B-   9D 53 BF    STA   $BF53,X
BF4E-   CA          DEX
BF4F-   CA          DEX
BF50-   10 ED       BPL   $BF3F
BF52-   60          RTS
BF53-   01 0C 0F 05 22 04

Continuing from $BED4...

; After swapping the RWTS parameters,
; the accumulator has the original RWTS
; command. Check if it's a format
; command -- i.e. the game is trying to
; initialize a disk for saved games.
BED4-   C9 04       CMP   #$04

; no (whew)
BED6-   D0 14       BNE   $BEEC

; Yes, this is a format command. Ugh.
; We're not really going to format the
; disk. Instead, we're going to assume
; the disk is already formatted, and
; write the disk volume as regular data
; in our special sector (T22,S0F).
; First, convert $04 (format command)
; to $02 (write command).
BED8-   4A          LSR
BED9-   91 48       STA   ($48),Y

; Get address from RWTS parameter table
BEDB-   A0 08       LDY   #$08
BEDD-   B1 48       LDA   ($48),Y
BEDF-   85 3E       STA   $3E
BEE1-   C8          INY
BEE2-   B1 48       LDA   ($48),Y
BEE4-   85 3F       STA   $3F

; The expected disk volume of a save
; game disk is $0F, so set that as the
; first byte in the buffer
BEE6-   A0 00       LDY   #$00
BEE8-   A9 0F       LDA   #$0F
BEEA-   91 3E       STA   ($3E),Y

; Execution continues here regardless
; (possibly from the branch at $BED6,
; or by falling through after setting
; up the fake format command)
BEEC-   20 3A BF    JSR   $BF3A

*BF3AL

; call the real RWTS
BF3A-   20 04 BD    JSR   $BD04

After the real RWTS returns, $BF3A will
fall through to $BF3D, which is the
routine we called earlier to swap the
track/sector/command in the RWTS
parameter table. So now they've swapped
back to their original values.

Continuing from $BEEF...

; I just want to point out that this
; address spells "BEEF", which is funny
; but irrelevant to the task at hand.
; "Where's the $BEEF?" "There it is!"
; Never mind, this entire comment was a
; misteak.
BEEF-   A9 01       LDA   #$01

; Restore read command
BEF1-   8D 53 BF    STA   $BF53

; If the real RWTS came back with an
; error, cancel all the magic and
; propagate the error back to the
; caller
BEF4-   B0 35       BCS   $BF2B

; Real RWTS is OK, so on with the show!
; Y = #$0C when the real RWTS returns,
; which is handy because we want to get
; the RWTS command from the parameter
; table.
BEF6-   B1 48       LDA   ($48),Y
BEF8-   AA          TAX

; Set the error in the RWTS parameter
; table to "disk volume mismatch."
; (This will only be checked by the
; caller if we return with the carry
; bit set, which we haven't decided
; yet, so it's safe to do this now.)
BEF9-   C8          INY
BEFA-   A9 20       LDA   #$20
BEFC-   91 48       STA   ($48),Y

; Get the fake disk volume from the
; first byte of the sector data
BEFE-   A0 00       LDY   #$00
BF00-   B1 3E       LDA   ($3E),Y

; Save it for next time
BF02-   8D 0D BF    STA   $BF0D

; If the original RWTS command was $04
; (format), we've done all the magic
; we're going to do today, so tell the
; caller that it worked and be happy.
BF05-   E0 04       CPX   #$04
BF07-   18          CLC
BF08-   F0 21       BEQ   $BF2B

; These next two instructions are self-
; modifying code. $BF0B is the disk
; volume that was originally requested
; (set at $BEC2), and $BF0D is the disk
; volume that we want the caller to
; believe is currently in the drive
; (set at $BF02).
BF0A-   A9 01       LDA   #$01
BF0C-   C9 01       CMP   #$01

; If they don't match, set the carry to
; indicate an error (we already set the
; RWTS error code to "disk volume
; mismatch, at $BEFC), then exit via
; the cleanup routine at $BF2B which
; will complete the illusion by setting
; the requested and found disk volume
; in the RWTS parameter table.
BF0E-   38          SEC
BF0F-   D0 1A       BNE   $BF2B

There are 4 possible ways we can end up
here.

  1) The caller requested a wildcard
     disk volume ($00), so we branched
     from $BEB8.

  2) The caller requested disk volume
     $FE, so we branched from $BEC5.

  3) The caller requested a disk volume
     of one of the game disks ($01-$05,
     or $0F to read from the save game
     disk), but it was the same disk
     volume as the previous RWTS call.
     We assume the correct disk is
     already in the drive and branched
     here from $BECF.

  4) The caller requested a disk volume
     of one of the game disks, it was
     different from the previous RWTS
     call, we did our magic to get the
     fake volume from T22,S0F and
     discovered that the correct disk
     is now in the drive, so we fell
     through from $BF0F.

; Execute the original RWTS command
BF11-   20 04 BD    JSR   $BD04

; Didn't work, propagate the error and
; exit
BF14-   B0 15       BCS   $BF2B

Hooray! The original RWTS command
succeeded! Just one last thing...

                   ~

; Was this the game trying to read
; T22,S0F?
BF16-   A0 04       LDY   #$04
BF18-   B1 48       LDA   ($48),Y
BF1A-   C9 22       CMP   #$22
BF1C-   D0 0C       BNE   $BF2A
BF1E-   C8          INY
BF1F-   B1 48       LDA   ($48),Y
BF21-   C9 0F       CMP   #$0F
BF23-   D0 05       BNE   $BF2A

; Yes, which means the first byte of
; the sector data we just read is the
; fake disk volume. Change that to the
; real data before returning to the
; caller. Presto-chango!
BF25-   A9 00       LDA   #$00
BF27-   A8          TAY
BF28-   91 3E       STA   ($3E),Y

[In this game, all disks have a #$00
 as the first byte of T22,S0F, so
 swapping out the real sector data is
 easy -- it's always #$00! But if that
 weren't the case, we could keep a map
 of expected data indexed by disk
 volume. That may be required if we use
 this code for other games.]

[Hint: we are definitely going to use
 this code for other games.]

[This technique is called 4shadowing.]

; Reset the carry to tell the caller
; that the RWTS command succeeded. (The
; CMPs to check the track and sector
; messed it up.)
BF2A-   18          CLC

All code paths lead here. This is the
final cleanup that we always do before
returning to the caller: setting the
requested and found disk volumes in the
RWTS parameter table. This does not
affect the carry bit.

BF2B-   A0 03       LDY   #$03
BF2D-   AD 0B BF    LDA   $BF0B
BF30-   91 48       STA   ($48),Y
BF32-   A0 0E       LDY   #$0E
BF34-   AD 0D BF    LDA   $BF0D
BF37-   91 48       STA   ($48),Y
BF39-   60          RTS

Now, each disk gets its own magic byte
on track $22, sector $0F:

disk 1A: T22,S0F,$00: 00 -> 01
disk 1B: T22,S0F,$00: 00 -> 02
disk 2A: T22,S0F,$00: 00 -> 03
disk 2B: T22,S0F,$00: 00 -> 04
disk 3A: T22,S0F,$00: 00 -> 05

Finally, we need one single patch. All
RWTS calls go through $B7B5, which in
turn calls $BD00. If we change that to
call $BEB0 instead, the illusion will
be complete.

T00,S01,$B8: 00BD -> B0BE

Quod erat liberandum.

---------------------------------------
A 4am and san inc crack        No. 1613
------------------EOF------------------
